
//
// 110515 - AH - Created.
// 110516 - AH - (Wesselstuen *) Missing java doc.
// 110704 - AH - (Hector) Added random spacing and line breaks.
// 110820a- AH - (France, Bordeaux, Ares *) Renamed from ColorPane and added 
//               some java doc.
// 110820b- AH - (France, Bordeaux, Ares *) Removed commented away old main 
//               test program.
// 120226a- AH - Selection get toString().
// 120226b- AH - Fixed that complete soekrat names was not picked, only words
//               in names. Now uses # to find boundaries.
//

package org.soekrat.gui.swing.chat.objectconversation;

import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;

import javax.swing.JTextPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;

import org.soekrat.engine.pit.Pit;

/**
 * <p>
 *   Pane for text that may be colored and words are clicked. 
 * </p>
 * <p>
 *   Used internally to implement 
 *   {@link ConversationComponent} hence package scope.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC) 
 */
@SuppressWarnings("serial")
final class NameCloudPane extends JTextPane 
{
  /**
   * <p>
   *   Interface for objects to receive selected words.
   * </p>
   * @author Arne Halvorsen (AH)
   * @author Laetitia Le Chatton (LLC)
   */
  interface Observer
  {
    /**
     * <p>
     *   Invoked when word been selected.
     * </p>
     * @param word Selected word.
     */
    void selected(String word);
    
  }
  
  private Observer m_observer=null;
  
  private final ConversationComponent m_cc;
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param cc {@link ConversationComponent} used.
   */
  NameCloudPane(ConversationComponent cc)
  { 
    if (cc==null)
    {
      throw new NullPointerException("ontology");
    }
    
    m_cc=cc;
    
    addMouseListener
    (
      new MouseAdapter() 
      {
        public void mouseClicked(MouseEvent e) 
        { 
          int pos=getCaretPosition();
          
          String txt=getText();
          
          Selection sel=getSelection(getText(), pos);
          
          if (sel!=null)
          {
            if (sel.isNote())
            {
              String name=scanForNameFromNoteMark(txt, sel.getPos());
              if (name!=null)
              {
                m_cc.setNote(name);
              }
            }
            else
            {
              String word=sel.getName();
              if (word.length()!=0) 
              {
                if (m_observer!=null) m_observer.selected(word);
              }
            }
          }
        }
      }
    );
  }
  
  /**
   * <p>
   *   Sets
   *   {@link Observer} to receive selected words.
   * </p>
   * @param observer {@link Observer} to set, {@code null} if to clear.
   */
  void setObserver(Observer observer){ m_observer=observer; }
  
  /**
   * <p>
   *   Appends name to text.
   * </p>
   * @param c    {@link Color} that should be used.
   * @param name Name to append.
   */
  void append(Color c, String name)
  {
    boolean empty=getDocument().getLength()==0;
    if (empty)
    {
      String sp=getSpacing();
      append_(Color.WHITE, sp);
      append_(c, name); 
    }
    else
    {
      if (breakLine())
      {
        append_(Color.WHITE, "\n");
      }
      String sp=getSpacing();
      append_(Color.WHITE, sp);
      append_(c, name);
    }
    
    addNoteMark(name);
  }
  
  /**
   * <p>
   *   Called from 
   *   {@link #append(Color, String)} to set note mark '#'.
   * </p>
   * @param name Name to set for.
   */
  private void addNoteMark(String name)
  {
    String ns=(name==null) ? "" : name.trim();
    if (ns.length()==0) return;
    
    Pit pit=m_cc.getSoekratEngine().getPit();
    String ontology=m_cc.getOntology();
    boolean noteExists=pit.hasNoteContent(name, ontology);
    
    Color c=noteExists ? Color.BLUE : Color.BLACK;
    
    append_(c, " #");
  }
  
  /**
   * <p>
   *   Appends text.
   * </p>
   * @param c {@link Color} of text.
   * @param s Text.
   */
  private void append_(Color c, String s) 
  { 
    // better implementation--uses StyleContext
    StyleContext sc=StyleContext.getDefaultStyleContext();
    AttributeSet aset=sc.addAttribute(SimpleAttributeSet.EMPTY,
                                        StyleConstants.Foreground, c);
  
    int len=getDocument().getLength(); // same value as getText().length();  
    setCaretPosition(len);  // place caret at the end (with no selection)
    
    setCharacterAttributes(aset, false);
    replaceSelection(s); // there is no selection, so inserts at caret
  }
  
  private final Random m_rnd=new Random();
  
  private String getSpacing()
  {
    int n=m_rnd.nextInt(7)+3;
    StringBuilder sb=new StringBuilder();
    for (int i=0; i<n; i++) sb.append(' ');
    return sb.toString();
  } 
  
  private boolean breakLine()
  {
    return m_rnd.nextBoolean();
  }
  
  /**
   * <p>
   *   Return type of
   *   {@link NameCloudPane#getSelection(String, int)}.
   * </p>
   * @author Arne Halvorsen (AH)
   * @author Laetitia Le Chatton (LLC)
   */
  private static class Selection
  {
    private final boolean m_note;
    private final int m_pos;
    private final String m_name;
    
    Selection(int pos){ m_note=true; m_pos=pos; m_name=null; }
    
    Selection(int pos, String name){ m_note=false; m_pos=pos; m_name=name; }
    
    /**
     * <p>
     *   Tells if note mark been selected.
     * </p>
     * @return {@code true} if is the case, {@code false} if is not the case.
     */
    boolean isNote(){ return m_note; }
    
    /**
     * <p>
     *   Gets position for selection; where end user clicked.
     * </p>
     * @return Position.
     */
    int getPos(){ return m_pos; }
    
    /**
     * <p>
     *   Gets name selected.
     * </p>
     * @return Name or {@code null} if note mark selected
     *         ({@link #isNote()}{@code ==true}).
     */
    String getName(){ return m_name; }
    
    @Override
    public String toString() 
    {
      StringBuilder sb=new StringBuilder();
      sb.append("note=").append(m_note).append(", pos=").append(m_pos)
        .append(", name").append(m_name);
      return sb.toString();
    }
  }
  
  //
  //
  
  private static Selection getSelection(String txt, int pos)
  {
    if (txt==null)
    {
      throw new NullPointerException("txt");
    }
    
    int n=txt.length();
    
    if (pos<0 || pos>=n) return null;
    
    char c=txt.charAt(pos);
    
    if (c=='#') return new Selection(pos);
    
    if (!isWordLetter(c)) return null;
    
    int start=findSoekratNameStart(txt, pos);
    int end=findSoekratNameEnd(txt, pos);
    String sname=txt.substring(start, end).trim();
    return new Selection(pos, sname);
    
    //int start=findWordStart(txt, pos);
    //int end=findWordEnd(txt, pos);
    //return new Selection(pos, txt.substring(start, end));
  }
  
  private String scanForNameFromNoteMark(String txt, int pos)
  {
    StringBuilder sb=new StringBuilder();
    
    boolean done=false;
    
    boolean started=false;
    
    while (pos>-1 && !done)
    {
      char c=txt.charAt(pos);
      if (isWordLetter(c))
      {
        started=true;
        sb.append(c);
      }
      else
      {
        if (started) done=true;
      }
      pos--;
    }
    
    String backwards=sb.toString();
    backwards=(backwards==null) ? "" : backwards.trim();
    int n=backwards.length();
    sb.setLength(0);
    for (int i=n-1; i>=0; i--)
    {
      char c=backwards.charAt(i);
      sb.append(c);
    }
    return sb.toString();
  }
  
  private static int findSoekratNameStart(String txt, int pos)
  {
    char curr=txt.charAt(pos);
    while (pos>=0 && curr!='#')
    {
      pos--;
      if (pos>=0) curr=txt.charAt(pos);
    }
    return pos+1;
  }
  
  /* DO NOT REMOVE
  private static int findWordStart(String txt, int pos)
  {
    char curr=txt.charAt(pos);
    while (isWordLetter(curr) && pos>=0) 
    {
      pos--;
      if (pos>=0) curr=txt.charAt(pos);
    }
    return pos+1;
  }
  */
  
  private static int findSoekratNameEnd(String txt, int pos)
  {
    char curr=txt.charAt(pos);
    int l=txt.length();
    while (pos<l && curr!='#')
    {
      pos++;
      if (pos<l) curr=txt.charAt(pos);
    }
    return pos;
  }
  
  /* DO NOT REMOVE
  private static int findWordEnd(String txt, int pos)
  {
    char curr=txt.charAt(pos);
    int l=txt.length();
    while (isWordLetter(curr) && pos<l) 
    {
      pos++;
      if (pos<l) curr=txt.charAt(pos);
    }
    return pos;
  }
  */
  
  private static boolean isWordLetter(char c)
  {
    return Character.isLetter(c) || Character.isDigit(c);
  }

}
